问题
延迟初始化(lazy initialization)是延迟到需要域的值时才将它初始化的这种行为。如果永远不需要这个值,这个域就永远不会被初始化。这种方法既适用于静态域,也适用于实例域。和大多数优化一样,不成熟的优化是大部分错误的源头。那么针对线程安全的延迟初始化有哪些可靠的方式?
答案
下面是正常初始化实例域的方式,但是要注意采用了final修饰符:
private final FildType field= computeFieldValue();
现在要对这个实例域进行延迟初始化,有这样几种方式:
同步方法:在实例化域值得时候,可以使用同步方法从而保证线程安全性,如:
private FieldType field; synchronized FieldType getField(){ if(field == null){ field = computeFieldValues(); } return field; }
静态内部类:为了减小上面这种方式的同步访问成本,可以采用静态内部类的方式,被称之为lazy initialization holder class 模式。在jvm的优化下,这种方式不仅可以达到延迟初始化的效果,也能保证线程安全。示例代码为:
private static class FieldHolder{ static final FieldType field = computeFieldValue(); } static FieldType getField(){ return FieldType.field; }
双重检测:这种模式避免了在初始化之后,再次访问这个域时的锁定开销(在普通的方法里面,会使用synchronized对方法进行同步,每次访问方法的时候都要进行锁定)。这种模式的思想是:两次检查域的值,第一次检查时不锁定,看看其是否初始化;第二次检查时锁定。只用当第二次检查时,表明其没有被初始化,才会调用computeFieldValue方法对其进行初始化。如果已经被初始化了,就不会锁定了,另外该域被声明为volatile非常重要,示例代码为:
private volatile FieldType field; public FieldType getField() { FieldType result = field; if (result == null) { synchronized (this) { result = field; if (result == null) { field = result = computeFieldValue(); } } } return result; }
结论
大多数正常的初始化都要优于延迟初始化。如果非要进行延迟初始化的话,针对实例域采用双重检测方式,针对静态域,可以利用静态内部类的第一次访问才进行初始化的特性,使用静态内部类来完成延迟初始化。